import py3func
import executor.abstractcmd as key
from executor.plc import VirtualPLC
from executor.shell import VirtualShell
from executor.keyboard import VirtualKeyboard
from cui import Enum_Btn_Status

LOG = py3func.make_logging(__file__)

class AbstractCmdParser:
    def __init__(self, dict_cmd):
        self.dict_cmd_data = dict_cmd

        # @attribute
        self.executor_kbd = VirtualKeyboard()  # Singleton
        self.executor_plc = VirtualPLC()  # Singleton
        self.executor_shell = VirtualShell()  # Singleton

    def _exec_shell(self, key_status):
        for item in self.dict_cmd_data["shell"]:
            if "cmd" not in item[1]:
                str_btn_id = " -- btn[%s]" % self.btn_id if hasattr(self, "btn_id") else ""
                raise Exception("Unfinished Shell command: no [cmd] found" + str_btn_id)
            self.executor_shell.update_command(item[1]["cmd"])
            self.executor_shell.key_exec(key_status)

    def _exec_plc(self, key_status):
        for item in self.dict_cmd_data["plc"]:
            if "input" not in item[1]:
                str_btn_id = " -- btn[%s]" % self.btn_id if hasattr(self, "btn_id") else ""
                raise Exception("Unfinished PLC command: no [input] found" + str_btn_id)
            output_addr = item[1]["output"] if "output" in item[1] else None
            self.executor_plc.update_input_address(item[1]["input"], output_addr)
            self.executor_plc.key_exec(key_status)

    def _exec_keyboard(self, key_status):
        # check type　and sort the key_action
        for key_type, list_keys in self.dict_cmd_data.items():  # 此处比较特殊，是对整个dict_cmd_data进行遍历，丑陋??
            if key_type in key.Enum_Key_Type.keys():  # {"key_char", "key_mode", ...}
                for key_attr in list_keys:
                    # 执行键盘命令
                    self.executor_kbd.key_exec(key_type, key_attr[1]["key"], key_status)

    def parse_and_execute(self, key_status):  # 通过层叠的if判断，丑陋??
        if not self.dict_cmd_data:
            str_btn_id = " -- btn[%s]" % self.btn_id if hasattr(self, "btn_id") else ""
            raise Exception("No data found in command" + str_btn_id)

        # 允许多个cmd_type出现在同一btn中（如Key_char+key_mode+plc+shell），但不建议这样做；
        # 由于xml节点是无序的，对于 Shift+A 这一类的组合，无法确定组合顺序，可能产生不确定行为；
        # 组合键的逻辑应该显式调用key_mode后，再调用key_char，而不是将两个功能拼凑在一起。
        if self.dict_cmd_data.keys() & key.Enum_Key_Type.keys():  # {"key_char", "key_mode", ...}
            self._exec_keyboard(key_status)

        if "plc" in self.dict_cmd_data:
            self._exec_plc(key_status)

        if "shell" in self.dict_cmd_data:
            self._exec_shell(key_status)


class DataMacroParser(AbstractCmdParser):
    """ 对于Macro，其执行命令时的key_status是根据xml定义，而非UI事件的传入 """
    def __init__(self, dict_cmd):
        super().__init__(dict_cmd)

    def _check_macro(self, dict_cmd):
        """ return the button pressing-status [Enum_Btn_Status] """
        if "isPressed" not in dict_cmd:  # for macro
            raise Exception("Fail to find the attr[isPressed] and it's necessary in Macro.")
        LOG.debug("Key_Macro [%s] is pressed? %s." % (dict_cmd["key"], dict_cmd["isPressed"]))
        key_status = Enum_Btn_Status["pressed"] if "true" == dict_cmd["isPressed"] \
                                                else Enum_Btn_Status["released"]  # 按宏定义的状态执行
        return key_status

    def _index_cmd_group(self):
        """ update the dict_cmd, return a list of cmd with index """
        pass

    def parse_and_execute(self):
        # 对于宏，根据sub_cmd中的attr["isPressed"]属性确定isReleased值
        # 此处理论上，需要增加<index>并依次调用cmd
        for key_type, list_cmd in self.dict_cmd_data.items():
            for tuple_cmd in list_cmd:
                key_status = self._check_macro(tuple_cmd[1])
                super().parse_and_execute(key_status)


class DataCmdParser(AbstractCmdParser):
    """ 解析 dict_cmd，分解动作，并调用命令执行器执行动作 """
    def __init__(self, btn_id, dict_cmd):
        super().__init__(dict_cmd)

        # @attribute
        self.btn_id = btn_id

    def execute_command(self, key_status):
        if "macro" in self.dict_cmd_data and key_status == Enum_Btn_Status["pressed"]:
            self._exec_macro()
        self.parse_and_execute(key_status)

    def _exec_macro(self):
        for item in self.dict_cmd_data["macro"]:
            macro_command = DataMacroParser(item[2])
            macro_command.parse_and_execute()
